package com.newfiber.workflow.config.diagram;

import java.awt.Color;
import java.awt.Font;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.Objects;
import javax.imageio.ImageIO;
import org.flowable.bpmn.model.AssociationDirection;
import org.flowable.bpmn.model.GraphicInfo;
import org.flowable.image.exception.FlowableImageException;
import org.flowable.image.impl.DefaultProcessDiagramCanvas;
import org.flowable.image.util.ReflectUtil;

/**
 * @author : X.K
 * @since : 2022/2/24 下午1:23
 */
public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas {

	protected static Color LABEL_COLOR = new Color(0, 0, 0);

	//font
	protected String activityFontName = "宋体";
	protected String labelFontName = "宋体";
	protected String annotationFontName = "宋体";
	protected Font font = null;{
		try {
			InputStream inputStream = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResourceAsStream("font/SIMSUN.TTC"));
			font = Font.createFont(Font.TRUETYPE_FONT, inputStream);
		} catch (Exception ignore) {}
	}

	private static volatile boolean flag = false;

	public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) {
		super(width, height, minX, minY, imageType);
	}

	public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType,
		String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
		super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName,
			customClassLoader);
	}

	public void drawHighLight(boolean isStartOrEnd, int x, int y, int width, int height, Color color) {
		Paint originalPaint = g.getPaint();
		Stroke originalStroke = g.getStroke();

		g.setPaint(color);
		g.setStroke(MULTI_INSTANCE_STROKE);
		if (isStartOrEnd) {// 开始、结束节点画圆
			g.drawOval(x, y, width, height);
		} else {// 非开始、结束节点画圆角矩形
			RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 5, 5);
			g.draw(rect);
		}
		g.setPaint(originalPaint);
		g.setStroke(originalStroke);
	}

	public void drawSequenceflow(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault,
		boolean highLighted, double scaleFactor, Color color) {
		drawConnection(xPoints, yPoints, conditional, isDefault, "sequenceFlow", AssociationDirection.ONE, highLighted,
			scaleFactor, color);
	}

	public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault,
		String connectionType, AssociationDirection associationDirection, boolean highLighted, double scaleFactor,
		Color color) {

		Paint originalPaint = g.getPaint();
		Stroke originalStroke = g.getStroke();

		g.setPaint(CONNECTION_COLOR);
		if (connectionType.equals("association")) {
			g.setStroke(ASSOCIATION_STROKE);
		} else if (highLighted) {
			g.setPaint(color);
			g.setStroke(HIGHLIGHT_FLOW_STROKE);
		}

		for (int i = 1; i < xPoints.length; i++) {
			Integer sourceX = xPoints[i - 1];
			Integer sourceY = yPoints[i - 1];
			Integer targetX = xPoints[i];
			Integer targetY = yPoints[i];
			Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY);
			g.draw(line);
		}

		if (isDefault) {
			Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
			drawDefaultSequenceFlowIndicator(line, scaleFactor);
		}

		if (conditional) {
			Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
			drawConditionalSequenceFlowIndicator(line, scaleFactor);
		}

		if (associationDirection.equals(AssociationDirection.ONE)
			|| associationDirection.equals(AssociationDirection.BOTH)) {
			Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2],
				xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]);
			drawArrowHead(line, scaleFactor);
		}
		if (associationDirection.equals(AssociationDirection.BOTH)) {
			Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]);
			drawArrowHead(line, scaleFactor);
		}
		g.setPaint(originalPaint);
		g.setStroke(originalStroke);
	}

	public void drawLabel(boolean highLighted, String text, GraphicInfo graphicInfo, boolean centered) {
		float interline = 1.0f;

		// text
		if (text != null && text.length() > 0) {
			Paint originalPaint = g.getPaint();
			Font originalFont = this.g.getFont();
			if (highLighted) {
				g.setPaint(FlowableWorkflowConstants.COLOR_NORMAL);
			} else {
				g.setPaint(LABEL_COLOR);
			}
			this.g.setFont(this.font);

			int wrapWidth = 100;
			int textY = (int) graphicInfo.getY();

			AttributedString as = new AttributedString(text);
			as.addAttribute(TextAttribute.FOREGROUND, g.getPaint());
			as.addAttribute(TextAttribute.FONT, g.getFont());
			AttributedCharacterIterator aci = as.getIterator();
			FontRenderContext frc = new FontRenderContext(null, true, false);
			LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);

			while (lbm.getPosition() < text.length()) {
				TextLayout tl = lbm.nextLayout(wrapWidth);
				textY += tl.getAscent();
				Rectangle2D bb = tl.getBounds();
				double tX = graphicInfo.getX();
				if (centered) {
					tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);
				}
				tl.draw(g, (float) tX, textY);
				textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent();
			}

			// restore originals
			this.g.setFont(originalFont);
			g.setPaint(originalPaint);
		}
	}

	@Override
	public BufferedImage generateBufferedImage(String imageType) {
		if (closed) {
			throw new FlowableImageException("ProcessDiagramGenerator already closed");
		}

		// Try to remove white space
		minX = (minX <= FlowableWorkflowConstants.PROCESS_PADDING) ? FlowableWorkflowConstants.PROCESS_PADDING : minX;
		minY = (minY <= FlowableWorkflowConstants.PROCESS_PADDING) ? FlowableWorkflowConstants.PROCESS_PADDING : minY;
		BufferedImage imageToSerialize = processDiagram;
		if (minX >= 0 && minY >= 0) {
			imageToSerialize = processDiagram.getSubimage(
				minX - FlowableWorkflowConstants.PROCESS_PADDING,
				minY - FlowableWorkflowConstants.PROCESS_PADDING,
				canvasWidth - minX + FlowableWorkflowConstants.PROCESS_PADDING,
				canvasHeight - minY + FlowableWorkflowConstants.PROCESS_PADDING);
		}
		return imageToSerialize;
	}

	@Override
	public void initialize(String imageType) {
		this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
		this.g = processDiagram.createGraphics();

		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setPaint(Color.black);

		Font font = this.font;
		if(null == font){
			font = new Font(activityFontName, Font.BOLD, FONT_SIZE);
		}
		this.g.setFont(font);
		this.fontMetrics = g.getFontMetrics();

		LABEL_FONT = this.font;
		ANNOTATION_FONT = this.font;
		//优化加载速度
		if(flag) {
			return;
		}
		try {
			USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/userTask.png", customClassLoader));
			SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/scriptTask.png", customClassLoader));
			SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/serviceTask.png", customClassLoader));
			RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/receiveTask.png", customClassLoader));
			SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/sendTask.png", customClassLoader));
			MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/manualTask.png", customClassLoader));
			BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/businessRuleTask.png", customClassLoader));
			SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/shellTask.png", customClassLoader));
			CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/camelTask.png", customClassLoader));
			MULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/muleTask.png", customClassLoader));

			TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/timer.png", customClassLoader));
			COMPENSATE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate-throw.png", customClassLoader));
			COMPENSATE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate.png", customClassLoader));
			ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error-throw.png", customClassLoader));
			ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error.png", customClassLoader));
			MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message-throw.png", customClassLoader));
			MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message.png", customClassLoader));
			SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal-throw.png", customClassLoader));
			SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal.png", customClassLoader));
			flag = true;
		} catch (IOException e) {
			flag = false;
			LOGGER.warn("Could not load image for process diagram creation: {}", e.getMessage());
		}
	}


}